神经网络量化入门--基本原理
最近打算写一个关于神经网络量化的入门教程,包括网络量化的基本原理、离线量化、量化训练,以及全量化模型的推理过程,最后我会用 pytorch 从零构建一个量化模型,帮助读者形成更深刻的理解。
之所以要写这系列教程,主要是想帮助初次接触量化的同学快速入门。笔者在刚开始接触模型量化时走了很多弯路,并且发现网上的资料和论文对初学者来说太不友好。目前学术界的量化方法都过于花俏,能落地的极少,工业界广泛使用的还是 Google TFLite 那一套量化方法,而 TFLite 对应的大部分资料都只告诉你如何使用,能讲清楚原理的也非常少。
这系列教程不会涉及学术上那些花俏的量化方法,主要是想介绍工业界用得最多的量化方案,即 TFLite 的量化原理,对应 Google 的论文 Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference。
话不多说,我们开始。这一章中,主要介绍网络量化的基本原理,以及推理的时候如何跑量化模型。
背景知识
量化并不是什么新知识,我们在对图像做预处理时就用到了量化。回想一下,我们通常会将一张 uint8 类型、数值范围在 0~255 的图片归一成 float32 类型、数值范围在 0.0~1.0 的张量,这个过程就是反量化。类似地,我们经常将网络输出的范围在 0.0~1.0 之间的张量调整成数值为 0~255、uint8 类型的图片数据,这个过程就是量化。
所以量化本质上只是对数值范围的重新调整,可以「粗略」理解为是一种线性映射。之所以加「粗略」二字,是因为有些论文会用非线性量化,但目前在工业界落地的还都是线性量化,所以本文只讨论线性量化的方案。
不过,可以明显看出,反量化一般没有信息损失,而量化一般都会有精度损失。这也非常好理解,float32 能保存的数值范围本身就比 uint8 多,因此必定有大量数值无法用 uint8 表示,只能四舍五入成 uint8 型的数值。量化模型和全精度模型的误差也来自四舍五入的 clip 操作。
这篇文章中会用到一些公式,这里我们用 表示浮点实数, 表示量化后的定点整数。浮点和整型之间的换算公式为:
需要强调的一点是,定点整数的 zero point 就代表浮点实数的 0,二者之间的换算不存在精度损失,这一点可以从公式 (2) 中看出来,把
矩阵运算的量化
由于卷积网络中的卷积层和全连接层本质上都是一堆矩阵乘法,因此我们先看如何将浮点运算上的矩阵转换为定点运算。
假设
整理一下可以得到:
这里要用到一个 trick。假设
很多刚接触量化的同学对这一点比较疑惑,下面我就用一个简单的示例说明这一点。我们把
这里面
假设
M = 0.0072474273418460
P = 7091
def multiply(n, M, P):
result = M * P
Mo = int(round(2 ** n * M)) # 这里不一定要四舍五入截断,因为python定点数不好表示才这样处理
approx_result = (Mo * P) >> n
print("n=%d, Mo=%d, approx=%f, error=%f"%\
(n, Mo, approx_result, result-approx_result))
for n in range(1, 16):
multiply(n, M, P)
输出:
n=1, Mo=0, approx=0.000000, error=51.391507
n=2, Mo=0, approx=0.000000, error=51.391507
n=3, Mo=0, approx=0.000000, error=51.391507
n=4, Mo=0, approx=0.000000, error=51.391507
n=5, Mo=0, approx=0.000000, error=51.391507
n=6, Mo=0, approx=0.000000, error=51.391507
n=7, Mo=1, approx=55.000000, error=-3.608493
n=8, Mo=2, approx=55.000000, error=-3.608493
n=9, Mo=4, approx=55.000000, error=-3.608493
n=10, Mo=7, approx=48.000000, error=3.391507
n=11, Mo=15, approx=51.000000, error=0.391507
n=12, Mo=30, approx=51.000000, error=0.391507
n=13, Mo=59, approx=51.000000, error=0.391507
n=14, Mo=119, approx=51.000000, error=0.391507
n=15, Mo=237, approx=51.000000, error=0.391507
可以看到,在 n=11、
卷积网络的量化
有了上面矩阵乘法的量化,我们就可以进一步尝试对卷积网络的量化。
假设一个这样的网络:
这个网络只有三个模块,现在需要把 conv、fc、relu 量化。
假设输入为
同样地,假设 conv、fc 的参数为
卷积运算和全连接层的本质都是矩阵运算,因此我们可以把卷积运算表示成 「这里先忽略加 bias 的操作,这一步同样可以量化,不过中间有一些 trick,我们在之后的文章再仔细研究」:
得到 conv 的输出后,我们不用反量化回
对于量化的 relu 来说,计算公式不再是
得到
可以看到,上面整个流程都是用定点运算实现的。我们在得到全精度的模型后,可以事先统计出 weight 以及中间各个 feature map 的 min、max,并以此计算出 scale 和 zero point,然后把 weight 量化成 int8/int16 型的整数后,整个网络便完成了量化,然后就可以依据上面的流程做量化推理了。
总结
这篇文章主要介绍了矩阵量化的原理,以及如何把矩阵量化运用到卷积网络中,实现全量化网络的计算。这中间忽略了很多细节,比如 relu 和 conv 的合并、激活函数的量化、量化训练的流程等。
后面的文章会继续补充一些细节,并通过从零搭建一个 pytorch 的量化模型来帮助读者更好地理解中间的过程。